
package com.joysuch.wwyt.core.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.zlg.common.enums.BusinessTypeEnum;
import com.google.common.collect.Lists;
import com.joysuch.wwyt.core.bean.BaseAttachmentBean;
import com.joysuch.wwyt.core.bean.FileChunkItem;
import com.joysuch.wwyt.core.bean.dto.FileChunkUploadDTO;
import com.joysuch.wwyt.core.bean.vo.FileChunkCheckVO;
import com.joysuch.wwyt.core.entity.BaseAttachmentRepo;
import com.joysuch.wwyt.core.enums.FileConvertStates;
import com.joysuch.wwyt.core.repository.BaseAttachmentRepoDao;
import com.joysuch.wwyt.core.service.BaseAttachmentRepositoryService;
import com.joysuch.wwyt.core.service.FileChunkItemService;
import com.joysuch.wwyt.core.service.FileConvertService;
import com.joysuch.wwyt.log.service.FileUploadLogService;
import com.joysuch.wwyt.util.Md5Util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

@Service()
@Slf4j
public class BaseAttachmentRepositoryServiceImpl implements BaseAttachmentRepositoryService {

    private static final Logger logger = LoggerFactory.getLogger(BaseImageRepositoryServiceImpl.class);

    private static final Lock lock = new ReentrantLock();

    @Autowired
    private BaseAttachmentRepoDao dao;
    @Autowired
    private FileConvertService fileConvertService;
    @Autowired
    private FileUploadLogService fileUploadLogService;

    @Autowired
    private FileChunkItemService fileChunkItemService;

    @Autowired
    private BaseAttachmentRepositoryServiceImpl baseAttachmentRepositoryService;

    @Value("${wwyt.base.serverUrl}")
    private String serverUrl;

    @Value("${wwyt.file.upload.dir}")
    private String fileUploadDir;

    public final Long defaultChunkSize = 20 * 1024 * 1024L;

    public final Long maxFileSize = 2 * 1024 * 1024 * 1024L;

    public Long saveFile(MultipartFile cover1, String uploadDir) throws IOException, Exception {
        String fingerPrint = generateFingerPrint(cover1.getInputStream());
        // 根据文件名称和指纹判断文件的唯一性
        List<BaseAttachmentRepo> rList = dao.findByFileNameAndFingerPrint(cover1.getOriginalFilename(), fingerPrint);
        if (!CollectionUtils.isEmpty(rList)) {
            logger.info("文件已存在,文件名:" + cover1.getOriginalFilename() + " 指纹:" + fingerPrint);
            return rList.get(0).getId();
        }
        BaseAttachmentRepo attachment = copyAndSaveFile(cover1.getInputStream(), cover1.getOriginalFilename(),
                uploadDir, fingerPrint);
        return attachment.getId();
    }

    public BaseAttachmentRepo copyAndSaveFile(InputStream input, String originFileName, String uploadDir,
                                              String fingerPrint) throws IOException {

        BaseAttachmentRepo attachment = new BaseAttachmentRepo();
        try {
            // 使用指纹作为名称
            String fileName = fingerPrint;// UUID.randomUUID().toString().replace("-", "");
            String extension = FilenameUtils.getExtension(originFileName);
            // 避免将文件存储在一个目录下，使用文件名前2个字符作为子文件夹
            String subdir = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
            String dir = uploadDir + File.separator + subdir;
            String relativePath = subdir + "/" + fileName + "." + extension;
            File newFile = new File(dir + File.separator + fileName + "." + extension);
            FileUtils.forceMkdir(new File(dir));
            // cover1.transferTo(newFile);
            FileUtils.copyInputStreamToFile(input, newFile);
            // 压缩文件，按照宽度一定的格式压缩文件
            // compressFile()
            attachment.setDownloadCount(0);
            attachment.setFileName(originFileName);
            attachment.setFileSize((int) newFile.length());
            attachment.setName(fileName);
            attachment.setPath(relativePath);
            attachment.setType(extension.toLowerCase());
            attachment.setFingerPrint(fingerPrint);
            attachment.setConvertState(FileConvertStates.NOT_START.getCode());
            attachment.setIntegrity(0);
            attachment.setRetryCount(0);
            dao.save(attachment);
            // 文件格式转换，默认是异步的
            fileConvertService.convert(attachment, newFile);
            fileUploadLogService.savaLog(BusinessTypeEnum.FILE.getBusinessCode(), BusinessTypeEnum.FILE.getBusinessName(), originFileName, 1, "文件上传成功");
            return attachment;
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            fileUploadLogService.savaLog(BusinessTypeEnum.FILE.getBusinessCode(), BusinessTypeEnum.FILE.getBusinessName(), originFileName, 0, "文件上传失败: message :" + e.getMessage() + "  exception :" + e);
        }
        return null;
    }

    private String generateFingerPrint(InputStream stream) throws IOException {
        return Md5Util.getMd5ByFile(stream);
    }

    @Override
    public BaseAttachmentRepo findById(Long id) {
        return dao.findById(id).orElse(null);
    }

    @Override
    public BaseAttachmentBean findByIdIntoBean(Long id) {
        BaseAttachmentBean bean = new BaseAttachmentBean();
        bean.setDownloadUrl(serverUrl + "/file/download?id=" + id);
        bean.setId(id);
        Optional<BaseAttachmentRepo> att = dao.findById(id);
        bean.setName("deleted_file");
        if (att.isPresent()) {
            bean.setIntegrity(att.get().getIntegrity());
            bean.setName(att.get().getFileName());
        }
        return bean;
    }

    @Override
    public List<BaseAttachmentBean> findByIds(List<Long> ids) {
        List<BaseAttachmentBean> beans = new ArrayList<>();
        List<BaseAttachmentRepo> list = dao.findByIds(ids);
        for (BaseAttachmentRepo repo : list) {
            BaseAttachmentBean bean = new BaseAttachmentBean();
            bean.setName(repo.getFileName());
            bean.setId(repo.getId());
            bean.setDownloadUrl(serverUrl + "/file/download?id=" + repo.getId());
            beans.add(bean);
        }
        return beans;
    }

    @Override
    public FileChunkCheckVO checkFile4ChunkIndex(FileChunkUploadDTO dto) {

        if (dto.getTotalSize() > maxFileSize) {
            throw new IllegalArgumentException("文件大小不超过2G");
        }

        // 文件不存在
        BaseAttachmentRepo baseAttachmentRepos = dao.getOneByFileNameAndFingerPrint(dto.getFilename(), dto.getIdentifier());
        // 未上传
        if (Objects.isNull(baseAttachmentRepos)) {
            return FileChunkCheckVO.builder()
                    .uploadedChunks(Lists.newArrayList())
                    .fileMd5(dto.getIdentifier())
                    .fileName(dto.getFilename())
                    .uploaded(false) // 文件不存在
                    .build();
        }


        // 已上传 or 部分上传
        List<FileChunkItem> fileChunkItems = fileChunkItemService.listByFileIdAndMd5(dto.getIdentifier(), baseAttachmentRepos.getId());

        return FileChunkCheckVO.builder()
                .uploadedChunks(fileChunkItems.stream().map(FileChunkItem::getCurrentChunk).collect(Collectors.toList()))
                .fileId(baseAttachmentRepos.getId())
                .fileMd5(dto.getIdentifier())
                .uploaded(baseAttachmentRepos.getIntegrity() == 0)
                .fileName(dto.getFilename())
                .build();
    }

    @Override
    public Long chunkUploadFile(FileChunkUploadDTO dto) {

        // 避免将文件存储在一个目录下，使用文件名前2个字符作为子文件夹
        String subdir = DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
        String basePath = fileUploadDir + File.separator + subdir;
        String extension = FilenameUtils.getExtension(dto.getFile().getOriginalFilename());
        String fullFileName = basePath + File.separator + dto.getIdentifier() + "." + extension;

        File uploadPath = new File(basePath);
        if (!uploadPath.exists()) {
            boolean mkResult = uploadPath.mkdirs();
            if (!mkResult) {
                log.error("创建文件夹失败");
                return dto.getFileId();
            }
        }

        try {
            // 单文件上传
            if (dto.getTotalChunks() == 1) {
                return saveFile(dto.getFile(), fileUploadDir);
            }
            // 保存文件
            uploadFileByRandomAccessFile(fullFileName, dto);

            // 保存文件记录以及分片记录
            return baseAttachmentRepositoryService.saveBaseAttachmentRepoDao(subdir, dto);
        } catch (Exception e) {
            // 记录日志
            fileUploadLogService.savaLog(BusinessTypeEnum.FILE.getBusinessCode(), BusinessTypeEnum.FILE.getBusinessName(), dto.getFile().getOriginalFilename(), 0, "文件上传失败: message :" + e.getMessage() + "  exception :" + e);
            return null;
        }
    }

    @Override
    public BaseAttachmentRepo findBaseAttachmentRepo(Integer retryCount) {
        BaseAttachmentRepo repo = dao.findBaseAttachmentRepo(retryCount);
        return repo;
    }

    public  Long saveBaseAttachmentRepoDao(String subdir, FileChunkUploadDTO dto) {

        lock.lock();

        try {
            String originalFilename = dto.getFile().getOriginalFilename();
            String extension = FilenameUtils.getExtension(originalFilename);
            String relativePath = subdir + File.separator + dto.getIdentifier() + "." + extension;
            // 文件id
            Long fileId = null;
            BaseAttachmentRepo baseAttachmentRepo = dao.getOneByFileNameAndFingerPrint(originalFilename, dto.getIdentifier());
            // 第一次上传
            if (Objects.isNull(baseAttachmentRepo)) {
                BaseAttachmentRepo attachment = new BaseAttachmentRepo();
                attachment.setDownloadCount(0);
                attachment.setFileName(originalFilename);
                attachment.setFileSize(dto.getTotalSize());
                attachment.setName(dto.getIdentifier());
                attachment.setPath(relativePath);
                attachment.setType(Objects.requireNonNull(extension).toLowerCase());
                attachment.setFingerPrint(dto.getIdentifier());
                attachment.setConvertState(FileConvertStates.NOT_START.getCode());
                // 分片上传默认状态1，文件不完整
                attachment.setIntegrity(1);
                // 文件转换状态
                //convert(attachment);
                attachment.setRetryCount(0);
                BaseAttachmentRepo attachmentRepo = dao.save(attachment);
                fileId = attachmentRepo.getId();
            }

            FileChunkItem fileChunkItem = new FileChunkItem();
            fileChunkItem.setChunkSize(dto.getCurrentChunkSize());
            fileChunkItem.setCurrentChunk(dto.getChunkNumber());
            fileChunkItem.setFileMd5(dto.getIdentifier());
            fileChunkItem.setTotalChunkSize(dto.getTotalChunks());
            fileChunkItem.setFileId(Objects.nonNull(fileId) ? fileId : baseAttachmentRepo.getId());

            // 保存分片信息
            fileChunkItemService.saveChunkItem(fileChunkItem);

            // 完成上传  -> 修改文件状态 标记文件是否完整
            if (dto.getTotalChunks().equals(dto.getChunkNumber())) {
                File newFile = new File(fileUploadDir + File.separator + baseAttachmentRepo.getPath());
                // 更新状态
                //dao.updateIntegrityById(baseAttachmentRepo.getId());
                baseAttachmentRepo.setIntegrity(0);
                fileConvertService.convert(baseAttachmentRepo, newFile);
                fileUploadLogService.savaLog(BusinessTypeEnum.FILE.getBusinessCode(), BusinessTypeEnum.FILE.getBusinessName(), originalFilename, 1, "文件上传成功");
            }

            return Objects.nonNull(fileId) ? fileId : baseAttachmentRepo.getId();
        } finally {
            lock.unlock();
        }
    }

    private void uploadFileByRandomAccessFile(String filePath, FileChunkUploadDTO dto) throws IOException {

        RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "rw");
        // 获取分片大小，定位文件索引，写入数据
        long chunkSize = dto.getChunkSize() == 0L ? defaultChunkSize : dto.getChunkSize().longValue();
        long offSet = chunkSize * (dto.getChunkNumber() - 1);
        randomAccessFile.seek(offSet);
        randomAccessFile.write(dto.getFile().getBytes());
    }

    private void convert(BaseAttachmentRepo attachmentRepo) {
        File newFile = new File(fileUploadDir + File.separator + attachmentRepo.getPath());
        String extension = FilenameUtils.getExtension(newFile.getName());
        boolean flag =  "mp4".equals(extension) || "avi".equals(extension)||"mp3".equals(extension)||"wav".equals(extension);

        if (flag) {
            attachmentRepo.setNeedConvert("Y");
        } else {
            attachmentRepo.setNeedConvert("N");
            attachmentRepo.setConvertState(FileConvertStates.DO_NOT_SUPPORT.getCode());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long saveFile(File file) throws IOException, Exception {
        FileInputStream input = new FileInputStream(file);
        String fingerPrint = generateFingerPrint(input);
        // 更具文件名称和指纹判断文件的唯一性
        List<BaseAttachmentRepo> rList = dao.findByFileNameAndFingerPrint(file.getName(),fingerPrint);
        if (!CollectionUtils.isEmpty(rList)) {
            logger.info("文件已存在,文件名:" + file.getName() + " 指纹:" + fingerPrint);
            return rList.get(0).getId();
        }
        // 新建流，上个流已被读取完
        BaseAttachmentRepo attachment = copyAndSaveFile(new FileInputStream(file), file.getName(), fileUploadDir, fingerPrint);
        return attachment.getId();
    }

}
